#pragma once

#include "znx_defs.h"
#include "znx_string.h"

#include <sys/un.h>
#include <sys/stat.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>

#define ZNX_INET_ADDRSTRLEN         (sizeof("255.255.255.255") - 1)
#define ZNX_INET6_ADDRSTRLEN                                    \
    (sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255") - 1)
#define ZNX_UNIX_ADDR_PATH_SIZE                                 \
    (sizeof(struct sockaddr_un) - offsetof(struct sockaddr_un, sun_path))
#define ZNX_UNIX_ADDRSIZE                                        \
    (sizeof("unix:") - 1 + ZNX_UNIX_ADDR_PATH_SIZE)

#define ZNX_INET_SOCKADDR_SIZE      (ZNX_INET_ADDRSTRLEN + sizeof(":65535"))
#define ZNX_INET6_SOCKADDR_SIZE     (ZNX_INET6_ADDRSTRLEN + sizeof("[]:65535"))


// MAX(ZNX_INET_SOCKADDR_SIZE, ZNX_INET6_SOCKADDR_SIZE,  ZNX_UNIX_ADDRSTRLEN)
#define ZNX_SOCKADDR_SIZE           ZNX_UNIX_ADDRSIZE

#define ZNX_UNIX_ADDRESS_PREFIX     "unix:"
#define ZNX_UNIX_ADDRESS_PREFIX_LEN (sizeof(ZNX_UNIX_ADDRESS_PREFIX) - 1)


typedef struct sockaddr             znx_sockaddr_t;
typedef struct sockaddr_in          znx_sockaddr_in_t;
typedef struct sockaddr_in6         znx_sockaddr_in6_t;
typedef struct sockaddr_un          znx_sockaddr_un_t;


typedef union {
    znx_sockaddr_t                  sockaddr;
    znx_sockaddr_in_t               sockaddr_in;
    znx_sockaddr_in6_t              sockaddr_in6;
    znx_sockaddr_un_t               sockaddr_un;
} znx_address_t;


/**
 * @brief check current str is ipv4, ipv6, or unix socket.
 * the str does not contain port.
 * 
 * @param str is address str
 * @param len is address str length
 * @return int
 * if ret = 0, the str is invalid addr.
 * if ret = 1, the str is ipv4 addr.
 * if ret = 2, the str is ipv6 addr.
 * if ret = 3, the str is unix socket addr.
 */
static inline int
znx_inet_addr_check(const u_char *addr) {
    assert(addr != NULL);

    size_t addr_len = znx_strlen(addr);
    if (addr_len > ZNX_SOCKADDR_SIZE - 1) {
        return 0;
    }

    if (addr_len > ZNX_UNIX_ADDRESS_PREFIX_LEN &&
        znx_strncmp(addr, ZNX_UNIX_ADDRESS_PREFIX, ZNX_UNIX_ADDRESS_PREFIX_LEN) == 0) {
        return 3;
    }

    int     res = 0;
    char    buf[sizeof(struct in6_addr)];

    res = inet_pton(AF_INET, (const char *)addr, buf);
    if (res == 1) {
        return 1;
    }

    res = inet_pton(AF_INET6, (const char *)addr, buf);
    if (res == 1) {
        return 2;
    }

    return 0;
}


// only supports parsing address in the following format
// ""           -- we will consider it to be 0.0.0.0:0
// :port        -- we will consider it to be 0.0.0.0:port
// x.x.x.x or x.x.x.x:port
// [::] or [::]:port
// unix:path
// if the address does not contain port, it will be 0.

znx_bool_t znx_address_parser(u_char *addr_str,
    size_t addr_str_len, znx_address_t *address, int *port);


static inline int
znx_address_socklen(znx_address_t *address)
{
    if (address->sockaddr.sa_family == AF_INET) {
        return sizeof(znx_sockaddr_in_t);
    }

    if (address->sockaddr.sa_family == AF_INET6) {
        return sizeof(znx_sockaddr_in6_t);
    }

    if (address->sockaddr.sa_family == AF_UNIX) {
        return sizeof(znx_sockaddr_un_t);
    }

    return -1;
}


static inline znx_bool_t
znx_address_set_port(znx_address_t *address, int port)
{
    if (address->sockaddr.sa_family == AF_INET) {
        address->sockaddr_in.sin_port = htons((uint16_t)port);
        return ZNX_TRUE;
    }

    if (address->sockaddr.sa_family == AF_INET6) {
        address->sockaddr_in6.sin6_port = htons((uint16_t)port);
        return ZNX_TRUE;
    }

    return ZNX_FALSE;
}


znx_bool_t znx_address_init_by_sockaddr(znx_address_t *address,
    znx_sockaddr_t *sockaddr);

int znx_sockaddr_to_str(znx_sockaddr_t *addr, u_char *dest, int dest_size);


static inline int
znx_address_to_str(znx_address_t *address, u_char *dest, int dest_size)
{
    return znx_sockaddr_to_str(&address->sockaddr, dest, dest_size);
}


znx_bool_t znx_str_maybe_is_ipv4(znx_str_t *str);
